﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace NotifiableLinqTests.MetaGenerator
{
	public class Generator : IDisposable
	{
		readonly StringBuilder _result = new StringBuilder();

		#region Dispose and logger

		string Log
		{
			set
			{
				_log.WriteLine(value);
			}
		}

		readonly StreamWriter _log = new StreamWriter(File.OpenWrite(Path.GetTempFileName()));

		public void Dispose()
		{
			GC.SuppressFinalize(this);
			Dispose(true);
		}

		protected void Dispose(bool managed)
		{
			_log.Close();
		}

		~Generator()
		{
			Dispose(false);
		}

		#endregion

		string Out
		{
			set { _result.Append(value); }
		}

		public override string ToString()
		{
			Log = "Hello";
			Out = @"// file generated automatically
using System;
using System.Collections.Generic;
using NotifiableLinqTests.FrameWork;
using NotifiableLinqTests.MetaGenerator;
using NUnit.Framework;

namespace NotifiableLinqTests
{{
	public static class TargetValueProvider
	{{
		public static object GetValue(object provider)
		{{
			return (({0})provider).Value;
		}}
	}}

".Arg(TargetFramework.Current.ValueProvider);

			var ignore = new Dictionary<string, object> {
				{ "Average", null }, // tbd
				{ "Sum", null }, // tbd
				{ "Min", null }, // tbd
				{ "Max", null }, // tbd
				{ "Aggregate", null }, // tbd
				{ "OfType", null }, // tbd
				{ "Empty", null }, // additional test
				{ "Cast", null }, // additional test
			};
			var classNameUsageCounter = new Dictionary<string, int>();
			foreach (var mi in typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static))
			{
				if (ignore.ContainsKey(mi.Name))
				{
					continue;
				}

				var label = mi.ToString()
					.Replace("System.Collections.Generic.", "")
					.Replace("System.", "")
					.Replace("[", "<")
					.Replace("]", ">")
					.Replace("`1", "")
					.Replace("`2", "")
					.Replace("`3", "")
					.Replace("Boolean", "bool");

				var ms = TargetFramework.Current.ObservableEnumerable.GetMethods();

				var m = ms.SingleOrDefault(x => x.Name == mi.Name && ComareMethodParameters(x.GetParameters(), mi.GetParameters()));

				if (m == null)
				{
					Out = @"
#warning " + label + @"
";
					// continue;
				}

				var testClassName = mi.Name;
				var methodName = mi.Name;
				int i;
				if (!classNameUsageCounter.TryGetValue(testClassName, out i))
				{
					classNameUsageCounter.Add(testClassName, 1);
					i = 1;
				}
				else
				{
					classNameUsageCounter[testClassName] = ++i;
				}
				if (i > 1)
				{
					testClassName = testClassName + i;
				}

				var args = mi.GetParameters();
				var argsCallList = string.Join(", ", args.Select(x => (x.Name == "source") ? x.Name : ("_" + x.Name)).ToArray());

				var additionalParameters = new List<string>();
				var goodArgs = new bool[args.Length];
				int j = -1;
				foreach (var arg in args)
				{
					j++;
					if (arg.Name == "source")
					{
						if (arg.ParameterType.IsGenericType && arg.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
						{
							Log = "Source is generic";
							var ga1 = arg.ParameterType.GetGenericArguments()[0];
							if (ga1.IsValueType)
							{
								// bad one
								continue;
							}
						}
						goodArgs[j] = true;
						// continue;
					}
					else if (arg.Name == "predicate")
					{
						if(arg.ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
						{
							goodArgs[j] = true;
							additionalParameters.Add("readonly Func<IItem, bool> _predicate = x => x.Width > 5;");
						}
						if(arg.ParameterType.GetGenericTypeDefinition() == typeof(Func<,,>))
						{
							goodArgs[j] = true;
							additionalParameters.Add("readonly Func<IItem, int, bool> _predicate = (x, index)=> x.Width % 2 == 0 && index % 2 == 0;");
						}
						//if (arg.ParameterType.IsGenericParameter)
						//{
						//	var gtd = arg.ParameterType.GetGenericTypeDefinition();
						//	if (arg.ParameterType == typeof(Func<,>)) {}
						//}
						//throw new NotSupportedException();
					}
					else if (arg.Name == "selector")
					{
						Log = "Method {0} Parameter {1}".Arg(label, arg.Name);
						Log = "IsGenerig? = {0}".Arg(arg.ParameterType.IsGenericType);
						if (arg.ParameterType.IsGenericType)
						{
							Log = "Is Func<,>? = {0}".Arg(arg.ParameterType.GetGenericTypeDefinition() == typeof(Func<,>));
							if (arg.ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
							{
								var arg2 = arg.ParameterType.GetGenericArguments()[1];
								if (arg2.IsGenericType && arg2.GetGenericTypeDefinition() == typeof(IEnumerable<>))
								{
									// goodArgs[j] = true;
									// additionalParameters.Add("readonly Func<IItem, int[]> _selector = x => new []{x.Width};");
								}
								else
								{
									goodArgs[j] = true;
									additionalParameters.Add("readonly Func<IItem, int> _selector = x => x.Width;");
								}
							}
						}

					}
				}

				var sutBody = "return {0}.{1}({2});".Arg(TargetFramework.Current.ObservableEnumerableTypeName, methodName, argsCallList);
				var actualBody = "return System.Linq.Enumerable.{0}({1});".Arg(methodName,argsCallList);

				if (m == null)
				{
					actualBody = sutBody = "throw new NotImplementedException(\"Target Framework\");";
				}
				else if (!goodArgs.All(x => x))
				{
					actualBody = sutBody = "throw new NotImplementedException(\"Test\");";
				}

				Out = @"


	/// <summary>
	/// " + label + @"
	/// </summary>
	[TestFixture]
	public class " + testClassName + @"_test : ItemNotificationTestsBase
	{
		
		[Test]
		public void ExecuteTest()
		{
			base.ExecuteTestCore();
		}
	
		" + string.Join("\r\n", additionalParameters.ToArray()) // + (args.Any(x=>x.Name == "predicate") ? "readonly Func<IItem, bool> _predicate = x => x.Width > 5;" : "" )
					+ @"

		public override object QuerySut(IEnumerable<IItem> source)
		{{
			{0}
		}}".Arg(sutBody)+@"

		public override object QueryActual(IEnumerable<IItem> source)
		{{
			{0}
		}}".Arg(actualBody)+@"
	}

";
			}
			Out = "}";
			return _result.ToString();
		}

		public bool ComareMethodParameters(ParameterInfo[] arr1, ParameterInfo[] arr2)
		{
			if (arr1.Length != arr2.Length)
			{
				return false;
			}
			for (var i = 0; i < arr2.Length; i++)
			{
				if (!ComareType(arr1[i].ParameterType, arr2[i].ParameterType))
				{
					return false;
				}
			}
			return true;
		}

		public bool ComareType(Type type1, Type type2)
		{
			if (type1 == type2)
			{
				return true;
			}
			if (type1.IsGenericParameter && type2.IsGenericParameter)
			{
				return true;
			}
			if (type1.IsGenericType && type2.IsGenericType)
			{
				var type1Definition = type1.GetGenericTypeDefinition();
				var type2Definition = type2.GetGenericTypeDefinition();
				if (type1Definition == type2Definition)
				{
					var type1Genargs = type1.GetGenericArguments();
					var type2Genargs = type2.GetGenericArguments();
					for (var i = 0; i < type1Genargs.Length; i++)
					{
						if (!ComareType(type1Genargs[i], type2Genargs[i]))
						{
							return false;
						}
					}
					return true;
				}
			}
			return false;
		}
	}
}